#include <algorithm>
#include <deque>
#include <vector>
#include <iostream>

#include "dct_node.h"
#include "../contree/tetra.h"
#include "../contree/edgecache.h"

using namespace PBVP;

float DualNode::all_min = -1.0e10;		// a very small value
float DualNode::all_max = 1.0e10;		// a very large value

float SimScore::wGeo = 0.25f;
float SimScore::wShape = 0.1f;
float SimScore::wTopo = 0.05f;
float SimScore::wPot = 0.6f;

// Constructor
DualNode::DualNode(int _range) {
	range = _range;
	fmax = all_min;
	fmin = all_max;
	normVolume = -1;
	rawVolume = 0;
	volumeComputed = false;
	ncrit = 0;
	fint = 0;
	parent = NULL;
	match = NULL;
	p_below = new vector<DualNode *>;
	p_above = new vector<DualNode *>;
	pruned = false;

	// -1 for unknown start tetrahedron
	startTetra = -1;
}

// Destructor
DualNode::~DualNode (){
	delete p_below;
	delete p_above;

	std::vector<Attribute *>::iterator it;
	for (it = attributes.begin(); it != attributes.end(); ++it)
	{
		delete (*it);
	}
}

float DualNode::getFuncIntegral(int ip)
{
	PotentialAttribute* pPotAttr =  (PotentialAttribute *) attributes[3+ip];
	return pPotAttr->p_Integral;
}

void DualNode::merge(DualNode* p_node, int direct,  bool firstChild) {
	setFuncMin(p_node->fmin);
	setFuncMax(p_node->fmax);
	rawVolume = rawVolume + p_node->rawVolume;
	// normVolume = ((normVolume > 0)? normVolume:0) + p_node->normVolume;
	fint += p_node->fint;
	ncrit += p_node->ncrit;
	p_node->parent = this;
	match = NULL;
	if (firstChild)
	{
		bl = p_node->bl;
		bu = p_node->bu;
	} else
	{
		switch (direct)
		{
		case 0:
			bl += p_node->bl;
			bl -= p_node->bu;
			break;
		case 1:
			bu -= p_node->bl;
			bu += p_node->bu;
			break;
		default:
			assert(0);
		}
	}

	// moments = moments + p_node->moments;

	bool inUpperRange = (direct == 1)? true:false;
	if (firstChild)
	{
		std::vector<Attribute *>::iterator it;
		int i = 0;
		// raise each child attribute and insert it into the vector of the parent
		for (it = p_node->attributes.begin(); it != p_node->attributes.end(); ++it)
		{
			Attribute* attr = (*it);
			Attribute* parentAttr = attr->raise(inUpperRange);
			attributes.push_back(parentAttr);
			i++;
		}
		// set the start tetrahedron
		startTetra = p_node->startTetra;
	} else
	{
		std::vector<Attribute *>::iterator child_It, parent_It;
		for (child_It = p_node->attributes.begin(), parent_It = attributes.begin();
			child_It != p_node->attributes.end() && parent_It != attributes.end();
			++child_It, ++parent_It)
		{
			Attribute* parent_Attr = *parent_It;
			Attribute* child_Attr = *child_It;
			parent_Attr->merge(child_Attr, inUpperRange);
		}
	}
}


SimScore DualNode::similarity(DualNode *pNode)
{
	SimScore sim;

	// attribute 1: geoAttribute
	sim.geoScore = attributes[0]->simScore(pNode->attributes[0]);
	// attribute 2: shapeAttribute
	sim.shapeScore = attributes[1]->simScore(pNode->attributes[1]);
	// attribute 3: topoAttribute
	sim.topoScore = attributes[2]->simScore(pNode->attributes[2]);
	// attribute 4+: potAttribute
	if (attributes.size() > 3)
	{
		sim.potScore = attributes[3]->simScore(pNode->attributes[3]);
	}

	return sim;
}

void DualNode::print() const
{
	int k;
	printf("\nNode %d ---- %f, %f, [%f, %f], %d: ", id, normVolume, fint, fmin, fmax, ncrit);
	for (k = 0; k < lowerDegree(); k++)
	{
		printf(" %d ", lowerNeighbor(k)->id);
	}
	printf(" ---- ");
	for (k = 0; k < upperDegree(); k++)
	{
		printf(" %d ", upperNeighbor(k)->id);
	}
	printf("\n");

	std::cout << "Betti numbers: lower = " << bl << ", upper = " << bu << std::endl;

	std::vector<Attribute *>::const_iterator it;
	for (it = attributes.begin(); it != attributes.end(); ++it)
	{
		(*it)->print();
	}
}

QString DualNode::toQString() const
{
	QString str;

	str.append(QString("Node %1 ------ normalized volume = %2, func range = [%3, %4]\n")
		.arg(id).arg(normVolume).arg(fmin).arg(fmax));
	std::vector<Attribute *>::const_iterator it;
	for (it = attributes.begin(); it != attributes.end(); ++it)
	{
		str.append((*it)->toString().data());
	}
	return str;
}

QString SimScore::toQString() const
{
	QString str;
	
	str.append(QString("geoScore = %1, shapeScore = %2, topoScore = %3, potScore = %4\n")
		.arg(geoScore).arg(shapeScore).arg(topoScore).arg(potScore));
	str.append(QString("geoWeight = %1, shapeWeight = %2, topoWeight = %3, potWeight = %4\n")
		.arg(wGeo).arg(wShape).arg(wTopo).arg(wPot));
	str.append(QString("average score = %1\n").arg(average()));

	return str;
}